﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using LuaInterface;

namespace Twodi.Script
{
    /// <summary>
    /// Definição de um script Lua.
    /// </summary>
    public class LuaScript
    {
        /// <summary>
        /// Variaveis que serão utilizadas no Script;
        /// </summary>
        private Dictionary<String, object> variables;

        /// <summary>
        /// Script de comando Lua.
        /// </summary>
        private String script;

        /// <summary>
        /// Variaveis que serão utilizadas no Script;
        /// </summary>
        public String[] Variables
        {
            get
            {
                if (variables != null && variables.Count > 0) return this.variables.Keys.ToArray();
                else return null;
            }
        }

        /// <summary>
        /// Script de comando Lua.
        /// </summary>
        public String Script
        {
            get;
            set;
        }

        /// <summary>
        /// Construtor padrão.
        /// </summary>
        /// <param name="script">Script de comando Lua.</param>
        /// <param name="variables">Variaveis que serão utilizadas no Script;</param>
        public LuaScript(String script, params String[] variables)
        {
            Script = script;
            CreateVariables(variables);
        }

        /// <summary>
        /// Construtor padrão
        /// </summary>
        /// <param name="script">Script de comando Lua.</param>
        public LuaScript(String script)
        {
            CreateVariables(CheckVariables(ref script));
            Script = script;
        }

        /// <summary>
        /// Construtor padrão.
        /// </summary>
        public LuaScript()
        {

        }

        /// <summary>
        /// Verificar se existe variaveis definidas no script utilizando de "[" e "]".
        /// Será removido "[" e "]" do Script.
        /// </summary>
        /// <param name="script">Script a ser verificado.</param>
        /// <returns>Array de variaveis.</returns>
        public String[] CheckVariables(ref String script)
        {
            List<String> variables = new List<string>();
            String copy = script;

            //Scanneando o script.
            bool scanning = true;
            while (scanning)
            {
                //Procurar "["
                int begin = copy.IndexOf("[");

                //Se encontrado.
                if (begin != -1)
                {
                    //Procurar "]"
                    int end = copy.IndexOf("]");

                    //Se encontrado.
                    if (end != -1)
                    {
                        //Pular e retroceder um caracter ("[")
                        begin++;

                        //Calcular length
                        end -= begin;

                        //Pegar variavel.
                        String variable = copy.Substring(begin, end);

                        //Atualizar script
                        script = script.Replace("[" + variable + "]", variable);

                        //Adicionar a lista.
                        variables.Add(variable);

                        //Atualizar copia.
                        copy = script;
                    }
                    else { scanning = false; }
                }
                else { scanning = false; }
            }

            //Retornar array.
            return variables.ToArray();
        }

        /// <summary>
        /// Remover "[" e "]" do Script.
        /// </summary>
        /// <param name="script">Script a ter "[" e "]" removido.</param>
        public void ClearScript(ref String script)
        {
            script = script.Replace("[", "").Replace("]", "");
        }


        /// <summary>
        /// Criar variaveis.
        /// </summary>
        /// <param name="variables">Nome da variaveis.</param>
        public void CreateVariables(params String[] variables)
        {
            if (variables != null && variables.Length != 0)
            {
                this.variables = new Dictionary<string, object>();

                for (int i = 0; i < variables.Length; i++)
                {
                    this.variables.Add(variables[i], null);
                }
            }
        }

        /// <summary>
        /// Definir valor dos variaveis.
        /// </summary>
        /// <param name="variables">Valores a ser definido nas varaveis.</param>
        public void DefineVariables(params object[] variables)
        {
            if (variables == null || this.variables == null)
            {
                Console.WriteLine("Não há variaveis definidas, ou não há valores informados.");
            }
            else if (variables.Length != this.variables.Count)
            {
                Console.WriteLine("Quantidade de definições não confere com quantidade de variaveis.\n" +
                                    "Definida " + this.variables.Count + " variavel(eis) e informada " +
                                    variables.Length + " definição(ões).");
            }
            else
            {
                for (int i = 0; i < variables.Length; i++)
                {
                    this.variables[this.variables.ElementAt(i).Key] = variables[i];
                }
            }
        }

        /// <summary>
        /// Executar script na virtual machine.
        /// </summary>
        /// <param name="virtualMachine">Virtual machine do Lua aonde será executado o script.</param>
        /// <param name="variables">Pode ser usado para definir o valor da variavel de script.</param>
        public virtual object[] Execute(Lua virtualMachine, params object[] variables)
        {
            DefineVariables(variables);

            bool execute = true;
            for (int i = 0; i < this.variables.Count; i++)
            {
                KeyValuePair<String, object> element = this.variables.ElementAt(i);

                if (element.Value == null)
                {
                    Console.WriteLine("Variavel '" + element.Key + "' se encontra nula.");
                    execute = false;
                    break;
                }
                else
                {
                    virtualMachine[element.Key] = element.Value;
                }
            }

            try
            {
                if (execute)
                {
                    return virtualMachine.DoString(Script);
                }
                else
                {
                    Console.WriteLine("Script nao foi executado.");
                }
            }
            catch (Exception e)
            {
                return new object[1] { e.Message };
            }

            return null;
        }
    }
}
